import store from "@/store";

type EventCallback = (event?: Event | MessageEvent) => void;
type MessageCallback<T = any> = (data: T) => void;

interface ReconnectOptions {
    autoReconnect?: boolean;
    maxRetries?: number;
    backoff?: number;
    type?: string;
}

interface WebSocketClientOptions extends ReconnectOptions {
    url: string;
    protocols?: string | string[];
    onOpen?: EventCallback;
    onClose?: EventCallback;
    onError?: EventCallback;
    onMessage?: MessageCallback;
}

enum WebSocketState {
    CONNECTING = WebSocket.CONNECTING,
    OPEN = WebSocket.OPEN,
    CLOSING = WebSocket.CLOSING,
    CLOSED = WebSocket.CLOSED,
}

export class WebSocketClient {
    private ws: WebSocket | null = null;
    private timer: number | null = null;
    private type: string  = 'message';
    private retries = 0;
    private isManualClose = false; //标志区分主动关闭与意外断开，控制重连逻辑
    private user: any = {};
    private token: string = '';

    private readonly options: WebSocketClientOptions;
    private readonly defaultOptions: Required<ReconnectOptions> = {
        autoReconnect: true,
        maxRetries: 5,
        backoff: 1.5,
        type: 'message',
    };

    constructor(options: WebSocketClientOptions) {
        this.options = {
            ...this.defaultOptions,
            ...options,
        };
        const {user, token} = store.userStore;
        this.user = user;
        this.token = token;
    }

    public doConnect(): void {
        if (this.ws) {
            // 清理旧WebSocket实例
            this.ws.close(1000, 'restart');
            this.ws = null;
        }

        this.isManualClose = false;
        this.ws = new WebSocket(this.options.url, this.options.protocols);

        this.ws.onopen = (event) => {
            this.clearReconnectTimer();

            this.ws?.send(JSON.stringify({type: `AUTH`, data: this.token, sender: this.user.username}))
            this.options.onOpen?.(event);
        };

        this.ws.onmessage = (event) => {
            try {
                const data = JSON.parse(event.data);
                // 认证成功才算真正连接成功
                if (data.type === 'AUTH' && data.code === 200) {
                    console.debug("WebSocket连接已建立")
                }
                this.retries = 0;
                this.options.onMessage?.(data);
            } catch (error) {
                this.options.onMessage?.(event.data);
            }
        };

        this.ws.onclose = (event) => {
            console.debug("WebSocket连接已断开", event)
            this.options.onClose?.(event);

            if (this.options.autoReconnect && !this.isManualClose && event.code !== 4010) {
                this.doReconnect();
            }
        };

        this.ws.onerror = (event) => {
            this.options.onError?.(event);
        };
    }

    public send<T = any>(data: T, type?: string): void {
        if (this.getState() !== WebSocketState.OPEN) {
            console.error('WebSocket is not open');
            return;
        }

        try {
            this.ws?.send(JSON.stringify({type: type || this.options.type, data: data, sender: this.user.username}));
        } catch (error) {
            console.error('Failed to send message:', error);
            const errorEvent = new ErrorEvent('send_error', { error: error, message: '消息发送失败' });
            this.options.onError?.(errorEvent);
        }
    }

    public close(code?: number, reason?: string): void {
        this.isManualClose = true;
        this.ws?.close(code, reason);
    }

    private getState(): number {
        return this.ws?.readyState ?? WebSocketState.CLOSED;
    }

    private doReconnect(): void {
        this.clearReconnectTimer();

        //达到最大重试次数后停止重连并报错
        if (this.retries >= this.options.maxRetries!) {
            const error = new Error(`Max reconnect attempts (${this.options.maxRetries}) reached`);
            const errorEvent = new ErrorEvent('error', { error, message: error.message });
            this.options.onError?.(errorEvent);
            return;
        }

        //重试间隔
        const delay = 1000 * Math.pow(this.options.backoff!, this.retries);
        this.timer = window.setTimeout(() => {
            this.retries++;
            console.log(`Retry reconnect times: ${this.retries}/${this.options.maxRetries}`)
            this.doConnect();
        }, delay);
    }

    private clearReconnectTimer(): void {
        if (this.timer) {
            clearTimeout(this.timer);
            this.timer = null;
        }
    }
}